﻿namespace Pathfinder.Base.Constructs
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Reflection;
    using System.Windows;
    using System.Windows.Controls;
    using Bridge.Base.Constructs;
    using Bridge.Objects;
    using Bridge.UI.Utilities;
    using Pathfinder.Base.UI;

    /// <summary>
    /// Class for creating a Pathfinder Character.
    /// </summary>
    [Serializable]
    public class PathfinderCharacter : Character, IViewableCharacter
    {
        public PathfinderCharacter()
        {
        }

        #region Properties

        public string Statblock
        {
            get { return mStatblock; }
            set { CheckPathfinderPropertyChanged<string>("Statblock", ref mStatblock, ref value); }
        }

        public UIElement CharacterTooltip
        {
            get
            {
                TextBlock crTitleTextBlock = new TextBlock() { FontWeight = FontWeight.FromOpenTypeWeight(2), Width = 50, Text = "CR:", HorizontalAlignment = HorizontalAlignment.Right };
                TextBlock acTitleTextBlock = new TextBlock() { FontWeight = FontWeight.FromOpenTypeWeight(2), Width = 50, Text = "AC/TH/FF:", HorizontalAlignment = HorizontalAlignment.Right };
                TextBlock statTitleTextBlock = new TextBlock() { FontWeight = FontWeight.FromOpenTypeWeight(2), Width = 50, Text = "Stats:", HorizontalAlignment = HorizontalAlignment.Right };

                TextBlock crValueTextBlock = new TextBlock() { Text = CR };
                UIDisplayAttribute alignmentDisplay = Alignment.GetCustomAttribute<UIDisplayAttribute>();
                TextBlock typeText = new TextBlock() { Text = alignmentDisplay.DisplayName + " " + Size.ToString() + " " + CreatureType.ToString() };
                TextBlock acValueTextBlock = new TextBlock() { Text = FullAC + "/" + TouchAC + "/" + FlatFootedAC };
                TextBlock statValueTextBlock = new TextBlock() { Text = string.Format("{0}/{1}/{2}/{3}/{4}/{5}", TotalStats[StatType.Strength], TotalStats[StatType.Dexterity], TotalStats[StatType.Constitution], TotalStats[StatType.Intelligence], TotalStats[StatType.Wisdom], TotalStats[StatType.Charisma]) };

                StackPanel crStackPanel = new StackPanel() { Orientation = Orientation.Horizontal };
                crStackPanel.Children.Add(crTitleTextBlock);
                crStackPanel.Children.Add(crValueTextBlock);
                StackPanel acStackPanel = new StackPanel() { Orientation = Orientation.Horizontal };
                acStackPanel.Children.Add(acTitleTextBlock);
                acStackPanel.Children.Add(acValueTextBlock);
                StackPanel statStackPanel = new StackPanel() { Orientation = Orientation.Horizontal };
                statStackPanel.Children.Add(statTitleTextBlock);
                statStackPanel.Children.Add(statValueTextBlock);

                StackPanel mainStackPanel = new StackPanel();
                mainStackPanel.Children.Add(crStackPanel);
                mainStackPanel.Children.Add(typeText);
                mainStackPanel.Children.Add(acStackPanel);
                mainStackPanel.Children.Add(statStackPanel);

                return mainStackPanel;
            }
        }

        public override string CharacterType
        {
            get { return "Pathfinder"; }
        }

        public string Aura
        {
            get { return mAura; }
            set { CheckPathfinderPropertyChanged<string>("Aura", ref mAura, ref value); }
        }

        public string CR
        {
            get { return mCR; }
            set { CheckPathfinderPropertyChanged<string>("CR", ref mCR, ref value); }
        }

        public Alignment Alignment
        {
            get { return mAlignment; }
            set { CheckPathfinderPropertyChanged<Alignment>("Alignment", ref mAlignment, ref value); }
        }

        public SizeType Size
        {
            get { return mSize; }
            set { CheckPathfinderPropertyChanged<SizeType>("Size", ref mSize, ref value); }
        }

        public CreatureType CreatureType
        {
            get { return mCreatureType; }
            set { CheckPathfinderPropertyChanged<CreatureType>("CreatureType", ref mCreatureType, ref value); }
        }

        public ReadOnlyCollection<SubType> SubTypes
        {
            get { return mSubTypes.AsReadOnly(); }
        }

        public int InitBonus
        {
            get { return mInitBonus; }
            set { mInitBonus = value; }
        }

        public Dictionary<SightType, int> Sight
        {
            get { return mSight; }
        }

        public Dictionary<ACType, int> AC
        {
            get { return mAC; }
        }

        public Dictionary<int, int> HitDice
        {
            get { return mHitDice; }
        }

        public int BonusHP
        {
            get { return mBonusHP; }
            set { CheckPathfinderPropertyChanged<int>("BonusHP", ref mBonusHP, ref value); }
        }


        public int BaseHP
        {
            get { return mBaseHP; }
            set { CheckPathfinderPropertyChanged<int>("BaseHP", ref mBaseHP, ref value); }
        }

        public int FullAC
        {
            get
            {
                int actualAC = 10;
                foreach (ACType acType in mAC.Keys)
                {
                    actualAC += mAC[acType];
                }
                return actualAC;
            }
        }

        public int FlatFootedAC
        {
            get
            {
                int actualAC = 10;
                Type ACTypeType = typeof(ACType);
                foreach (ACType acType in mAC.Keys)
                {
                    FieldInfo fieldInfo = ACTypeType.GetField(acType.ToString());
                    if (fieldInfo != null)
                    {
                        ACTypeEffectAttribute attribute = (ACTypeEffectAttribute)Attribute.GetCustomAttribute(fieldInfo, typeof(ACTypeEffectAttribute));
                        if (attribute.EffectsFlatFooted)
                        {
                            actualAC += mAC[acType];
                        }
                    }
                }
                return actualAC;
            }
        }

        public int TouchAC
        {
            get
            {
                int actualAC = 10;
                Type ACTypeType = typeof(ACType);
                foreach (ACType acType in mAC.Keys)
                {
                    FieldInfo fieldInfo = ACTypeType.GetField(acType.ToString());
                    if (fieldInfo != null)
                    {
                        ACTypeEffectAttribute attribute = (ACTypeEffectAttribute)Attribute.GetCustomAttribute(fieldInfo, typeof(ACTypeEffectAttribute));
                        if (attribute.EffectsTouch)
                        {
                            actualAC += mAC[acType];
                        }
                    }
                }
                return actualAC;
            }
        }

        public Dictionary<DRType, int> DR
        {
            get { return mDR; }
        }

        public Dictionary<ERType, int> ER
        {
            get { return mER; }
        }

        public int SR
        {
            get { return mSR; }
            set { CheckPathfinderPropertyChanged<int>("SR", ref mSR, ref value); }
        }

        public Dictionary<SpeedType, int> Speed
        {
            get { return mSpeed; }
        }

        public FlyManuverability FlyManuverability
        {
            get { return mManuverability; }
            set { CheckPathfinderPropertyChanged<FlyManuverability>("FlyManuverability", ref mManuverability, ref value); }
        }

        public ReadOnlyCollection<Attack> Attacks
        {
            get { return mAttacks.AsReadOnly(); }
        }

        public string SpecialAttacks
        {
            get { return mSpecialAttacks; }
            set { CheckPathfinderPropertyChanged<string>("SpecialAttacks", ref mSpecialAttacks, ref value); }
        }

        public string SpecialQualities
        {
            get { return mSpecialQualities; }
            set { CheckPathfinderPropertyChanged<string>("SpecialQualities", ref mSpecialQualities, ref value); }
        }

        public Dictionary<StatType, int> BaseStats
        {
            get { return mBaseStats; }
        }

        public Dictionary<StatType, int> TotalStats
        {
            get { return mTotalStats; }
        }

        public int BAB
        {
            get { return mBAB; }
            set { CheckPathfinderPropertyChanged<int>("BAB", ref mBAB, ref value); }
        }

        public ReadOnlyCollection<Feat> Feats
        {
            get { return mFeats.AsReadOnly(); }
        }

        public ReadOnlyCollection<Skill> Skills
        {
            get { return mSkills.AsReadOnly(); }
        }

        public Dictionary<SaveType, int> Saves
        {
            get { return mSaves; }
        }

        public Dictionary<SaveType, int> BaseSaves
        {
            get { return mBaseSaves; }
        }

        public string DefensiveAbilities
        {
            get { return mDefensiveAbilities; }
            set { CheckPathfinderPropertyChanged<string>("DefensiveAbilities", ref mDefensiveAbilities, ref value); }
        }

        public ReadOnlyCollection<ImmunityType> Immunities
        {
            get { return mImmunities.AsReadOnly(); }
        }

        public int Space
        {
            get { return mSpace; }
            set { CheckPathfinderPropertyChanged<int>("Space", ref mSpace, ref value); }
        }

        public int Reach
        {
            get { return mReach; }
            set { CheckPathfinderPropertyChanged<int>("Reach", ref mReach, ref value); }
        }

        public string SpellLikeAbilities
        {
            get { return mSpellLikeAbilities; }
            set { CheckPathfinderPropertyChanged<string>("SpellLikeAbilities", ref mSpellLikeAbilities, ref value); }
        }

        public int CMB
        {
            get { return mCMB; }
            set { CheckPathfinderPropertyChanged<int>("CMB", ref mCMB, ref value); }
        }

        public int CMD
        {
            get { return mCMD; }
            set { CheckPathfinderPropertyChanged<int>("CMD", ref mCMD, ref value); }
        }

        public int TotalHitDice
        {
            get
            {
                int totalHitDice = 0;
                foreach (KeyValuePair<int, int> hd in mHitDice)
                {
                    totalHitDice += hd.Value;
                }
                return totalHitDice;
            }
        }

        public bool HasAgileManeuvers
        {
            get { return mHasAgileManeuvers; }
            set { CheckPathfinderPropertyChanged<bool>("HasAgileManeuvers", ref mHasAgileManeuvers, ref value); }
        }

        #endregion

        #region IViewableCharacter Members

        public CharacterView GetCharacterView()
        {
            return new PathfinderCharacterView(this);
        }

        #endregion

        public void SetAC(ACType acType, int number)
        {
            int ff = FlatFootedAC;
            int touch = TouchAC;
            int ac = FullAC;
            if (mAC.ContainsKey(acType))
            {
                mAC[acType] = number;
            }
            else
            {
                mAC.Add(acType, number);
            }
            NotifyPathfinderPropertyChanged("AC");
            if (FlatFootedAC != ff) NotifyPathfinderPropertyChanged("FlatFootedAC");
            if (TouchAC != touch) NotifyPathfinderPropertyChanged("TouchAC");
            if (FullAC != ac) NotifyPathfinderPropertyChanged("FullAC");

        }

        public void SetDR(DRType drType, int number)
        {
            if (DR.ContainsKey(drType))
            {
                mDR[drType] = number;
            }
            else
            {
                mDR.Add(drType, number);
            }
            NotifyPathfinderPropertyChanged("DR");
        }

        public void SetER(ERType erType, int number)
        {
            if (ER.ContainsKey(erType))
            {
                mER[erType] = number;
            }
            else
            {
                mER.Add(erType, number);
            }
            NotifyPathfinderPropertyChanged("ER");
        }

        public void SetSpeed(SpeedType speedType, int number)
        {
            if (Speed.ContainsKey(speedType))
            {
                mSpeed[speedType] = number;
            }
            else
            {
                mSpeed.Add(speedType, number);
            }
        }

        public void SetSkill(Skill skill)
        {
            if (!mSkills.Exists(skillEntry => skillEntry.Equals(skill)))
            {
                AddSkill(skill);
            }
            else
            {
                Skill skillInfo = mSkills.Find(skillEntry => skillEntry.Equals(skill));
                skillInfo.AlternateStatType = skill.AlternateStatType;
                skillInfo.IsClassSkill = skill.IsClassSkill;
                skillInfo.MiscBonus = skill.MiscBonus;
                skillInfo.Ranks = skill.Ranks;
            }
        }

        public void AddSkill(Skill skill)
        {
            if (string.IsNullOrEmpty(skill.Subtype)) return;
            if (mSkills.Exists(skillEntry => skillEntry.Equals(skill))) return;

            mSkills.Add(skill);
            NotifyPathfinderPropertyChanged("Skills");
        }

        public void RemoveSkill(Skill skill)
        {
            if (mSkills.Contains(skill))
            {
                mSkills.Remove(skill);
                if (skill.Subtype == null)
                {
                    mSkills.Add(Skill.CreateBaseSkill(skill.Name, null));
                }
                NotifyPathfinderPropertyChanged("Skills");
            }
        }

        public void AddFeat(Feat feat)
        {
            if (!mFeats.Contains(feat))
            {
                mFeats.Add(feat);
                NotifyPathfinderPropertyChanged("Feats");
            }
        }

        public void RemoveFeat(Feat feat)
        {
            if (mFeats.Contains(feat))
            {
                mFeats.Remove(feat);
                NotifyPathfinderPropertyChanged("Feats");
            }
        }

        public void AddBaseStat(StatType statType, int amount)
        {
            mBaseStats[statType] += amount;
        }

        public void AddTotalStat(StatType statType, int amount)
        {
            mTotalStats[statType] += amount;
        }

        public void SetBaseStat(StatType statType, int amount)
        {
            mBaseStats[statType] = amount;
        }

        public void SetTotalStat(StatType statType, int amount)
        {
            mTotalStats[statType] = amount;
        }

        public void AddAttack(Attack attack)
        {
            mAttacks.Add(attack);
            NotifyPathfinderPropertyChanged("Attacks");
        }

        public void RemoveAttack(Attack attack)
        {
            if (mAttacks.Contains(attack))
            {
                mAttacks.Remove(attack);
                NotifyPathfinderPropertyChanged("Attacks");
            }
        }

        public void AddSpeed(SpeedType speedType, int amount)
        {
            if (mSpeed.ContainsKey(speedType))
            {
                mSpeed[speedType] += amount;
            }
            else
            {
                mSpeed.Add(speedType, amount);
            }
            NotifyPathfinderPropertyChanged("Speed");
        }

        public void SubtractSpeed(SpeedType speedType, int amount)
        {
            if (mSpeed.ContainsKey(speedType))
            {
                if (mSpeed[speedType] <= amount)
                {
                    mSpeed.Remove(speedType);
                }
                else
                {
                    mSpeed[speedType] -= amount;
                }
                NotifyPathfinderPropertyChanged("Speed");
            }
        }

        public void AddER(ERType erType, int amount)
        {
            if (mER.ContainsKey(erType))
            {
                mER[erType] += amount;
            }
            else
            {
                mER.Add(erType, amount);
            }
            NotifyPathfinderPropertyChanged("ER");
        }

        public void SubtractER(ERType erType, int amount)
        {
            if (mER.ContainsKey(erType))
            {
                if (mER[erType] <= amount)
                {
                    mER.Remove(erType);
                }
                else
                {
                    mER[erType] -= amount;
                }
                NotifyPathfinderPropertyChanged("ER");
            }
        }

        public void AddDR(DRType drType, int amount)
        {
            if (mDR.ContainsKey(drType))
            {
                mDR[drType] += amount;
            }
            else
            {
                mDR.Add(drType, amount);
            }
            NotifyPathfinderPropertyChanged("DR");
        }

        public void SubtractDR(DRType drType, int amount)
        {
            if (mDR.ContainsKey(drType))
            {
                if (mDR[drType] <= amount)
                {
                    mDR.Remove(drType);
                }
                else
                {
                    mDR[drType] -= amount;
                }
                NotifyPathfinderPropertyChanged("DR");
            }
        }

        public void RemoveDR(DRType drType)
        {
            if (mDR.ContainsKey(drType))
            {
                mDR.Remove(drType);
                NotifyPathfinderPropertyChanged("DR");
            }
        }

        public void RemoveER(ERType erType)
        {
            if (mER.ContainsKey(erType))
            {
                mER.Remove(erType);
                NotifyPathfinderPropertyChanged("ER");
            }
        }

        public void AddImmunity(ImmunityType immunityType)
        {
            if (!mImmunities.Contains(immunityType))
            {
                mImmunities.Add(immunityType);
                NotifyPathfinderPropertyChanged("Immunities");
            }
        }

        public void RemoveImmunity(ImmunityType immunityType)
        {
            if (mImmunities.Contains(immunityType))
            {
                mImmunities.Remove(immunityType);
                NotifyPathfinderPropertyChanged("Immunities");
            }
        }

        public void RemoveSpeed(SpeedType speedType)
        {
            if (mSpeed.ContainsKey(speedType))
            {
                mSpeed.Remove(speedType);
                NotifyPathfinderPropertyChanged("Speed");
            }
        }

        public void AddAC(ACType acType, int amount)
        {
            int ff = FlatFootedAC;
            int touch = TouchAC;
            int ac = FullAC;
            if (mAC.ContainsKey(acType))
            {
                mAC[acType] += amount;
            }
            else
            {
                mAC.Add(acType, amount);
            }
            NotifyPathfinderPropertyChanged("AC");
            if (FlatFootedAC != ff) NotifyPathfinderPropertyChanged("FlatFootedAC");
            if (TouchAC != touch) NotifyPathfinderPropertyChanged("TouchAC");
            if (FullAC != ac) NotifyPathfinderPropertyChanged("FullAC");
        }

        public void RemoveAC(ACType acType)
        {
            if (mAC.ContainsKey(acType))
            {
                int ff = FlatFootedAC;
                int touch = TouchAC;
                int ac = FullAC;
                mAC.Remove(acType);
                NotifyPathfinderPropertyChanged("AC");
                if (FlatFootedAC != ff) NotifyPathfinderPropertyChanged("FlatFootedAC");
                if (TouchAC != touch) NotifyPathfinderPropertyChanged("TouchAC");
                if (FullAC != ac) NotifyPathfinderPropertyChanged("FullAC");
            }
        }

        public void AddHitDice(int hitDice, int amount)
        {
            if (mHitDice.ContainsKey(hitDice))
            {
                mHitDice[hitDice] += amount;
            }
            else
            {
                mHitDice.Add(hitDice, amount);
            }
            NotifyPathfinderPropertyChanged("HitDice");
            NotifyPathfinderPropertyChanged("TotalHitDice");
        }

        public void SubtractHitDice(int hitDice, int amount)
        {
            if (mHitDice.ContainsKey(hitDice))
            {
                if (mHitDice[hitDice] <= amount)
                {
                    mHitDice.Remove(hitDice);
                }
                else
                {
                    mHitDice[hitDice] -= amount;
                }
                NotifyPathfinderPropertyChanged("HitDice");
                NotifyPathfinderPropertyChanged("TotalHitDice");
            }
        }

        public void RemoveSight(SightType sightType)
        {
            if (mSight.ContainsKey(sightType))
            {
                mSight.Remove(sightType);
                NotifyPathfinderPropertyChanged("Sight");
            }
        }

        public void AddSight(SightType sightType, int sightDistance)
        {
            if (!mSight.ContainsKey(sightType))
            {
                mSight.Add(sightType, sightDistance);
            }
            else
            {
                mSight[sightType] = sightDistance;
            }
            NotifyPathfinderPropertyChanged("Sight");
        }

        public void AddSubType(SubType subType)
        {
            if (!mSubTypes.Contains(subType))
            {
                mSubTypes.Add(subType);
                NotifyPathfinderPropertyChanged("SubTypes");
            }
        }

        public void RemoveSubType(SubType subType)
        {
            if (mSubTypes.Contains(subType))
            {
                mSubTypes.Remove(subType);
                NotifyPathfinderPropertyChanged("SubTypes");
            }
        }

        public void SetSight(SightType sightType, int number)
        {
            if (mSight.ContainsKey(sightType))
            {
                mSight[sightType] = number;
            }
            else
            {
                mSight.Add(sightType, number);
            }
            NotifyPathfinderPropertyChanged("Sight");
        }

        public void SetSave(SaveType saveType, int number)
        {
            mSaves[saveType] = number;
        }

        public void SetBaseSave(SaveType saveType, int number)
        {
            mBaseSaves[saveType] = number;
        }

        public void SetHitDice(int dice, int amount)
        {
            if (mHitDice.ContainsKey(dice))
            {
                mHitDice[dice] = amount;
            }
            else
            {
                mHitDice.Add(dice, amount);
            }
        }

        public bool HasFeat(string featName)
        {
            return mFeats.Find(feat => feat.Name.Equals(featName, StringComparison.OrdinalIgnoreCase)) != null;
        }

        public bool HasSkill(string skillName, string subskillName)
        {
            foreach (Skill skill in Skills)
            {
                if ((skill.Name.Equals(skillName, StringComparison.OrdinalIgnoreCase)) &&
                    ((string.IsNullOrEmpty(subskillName) && string.IsNullOrEmpty(skill.Subtype)) ||
                    (!string.IsNullOrEmpty(subskillName) && !string.IsNullOrEmpty(skill.Subtype) && subskillName.Equals(skill.Subtype, StringComparison.OrdinalIgnoreCase))))
                {
                    return true;
                }
            }
            return false;
        }

        private string mCR = "0";
        private string mAura = string.Empty;
        private string mSpecialAttacks = "";
        private string mSpecialQualities = "";
        private string mDefensiveAbilities = "";
        private string mSpellLikeAbilities = string.Empty;
        private string mStatblock = string.Empty;

        private SizeType mSize = SizeType.Medium;
        private CreatureType mCreatureType = CreatureType.Humanoid;
        private Alignment mAlignment = Alignment.TrueNeutral;
        private FlyManuverability mManuverability = FlyManuverability.NA;

        private int mInitBonus = 0;
        private int mBonusHP = 0;
        private int mBaseHP = 0;
        private int mSR = 0;
        private int mBAB = 0;
        private int mSpace = 5;
        private int mReach = 5;
        private int mCMB = 0;
        private int mCMD = 0;

        private bool mHasAgileManeuvers = false;

        private List<SubType> mSubTypes = new List<SubType>();
        private List<Attack> mAttacks = new List<Attack>();
        private List<Feat> mFeats = new List<Feat>();
        private List<Skill> mSkills = Skill.CreateSkillList();
        private List<ImmunityType> mImmunities = new List<ImmunityType>();

        private Dictionary<ACType, int> mAC = new Dictionary<ACType, int>();
        private Dictionary<SightType, int> mSight = new Dictionary<SightType, int>();
        private Dictionary<int, int> mHitDice = new Dictionary<int, int>();
        private Dictionary<DRType, int> mDR = new Dictionary<DRType, int>();
        private Dictionary<ERType, int> mER = new Dictionary<ERType, int>();
        private Dictionary<SpeedType, int> mSpeed = new Dictionary<SpeedType, int>();
        private Dictionary<StatType, int> mBaseStats =
            new Dictionary<StatType, int>() { 
                {StatType.Strength, 10},
                {StatType.Dexterity, 10},
                {StatType.Constitution, 10},
                {StatType.Intelligence, 10},
                {StatType.Wisdom, 10},
                {StatType.Charisma, 10}};
        private Dictionary<StatType, int> mTotalStats =
            new Dictionary<StatType, int>() {
                {StatType.Strength, 10},
                {StatType.Dexterity, 10},
                {StatType.Constitution, 10},
                {StatType.Intelligence, 10},
                {StatType.Wisdom, 10},
                {StatType.Charisma, 10}};
        private Dictionary<SaveType, int> mSaves =
            new Dictionary<SaveType, int>() {
                {SaveType.Fort, 0},
                {SaveType.Ref, 0},
                {SaveType.Will, 0}};
        private Dictionary<SaveType, int> mBaseSaves =
            new Dictionary<SaveType, int>() {
                {SaveType.Fort, 0},
                {SaveType.Ref, 0},
                {SaveType.Will, 0}};


        private void NotifyPathfinderPropertyChanged(string propertyName)
        {
            NotifyPropertyChanged(propertyName);
            NotifyPropertyChanged("CharacterTooltip");
        }

        private void CheckPathfinderPropertyChanged<T>(string propertyName, ref T oldValue, ref T newValue)
        {
            CheckPropertyChanged<T>(propertyName, ref oldValue, ref newValue);
            NotifyPropertyChanged("CharacterTooltip");
        }

    }
}
